class vue {
  constructor(options) {
    this.$data = options.data;
    // 调用数据劫持的方法
    observe(this.$data);
    // 代理 使得vm可以直接使用其中的属性
    Object.keys(this.$data).forEach((key) => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        set(newVal) {
          this.$data[key] = newVal;
        },
        get() {
          return this.$data[key];
        },
      });
    });
    // 调用模板编译的方法
    Compile(options.el, this);
  }
}

// 定义数据劫持的方法
function observe(obj) {
  const dep = new Dep();
  // 递归的终止条件
  if (!obj || typeof obj !== "object") return;

  Object.keys(obj).forEach((key) => {
    let val = obj[key];
    observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      set(newVal) {
        val = newVal;
        observe(val);
        dep.notify();
      },
      get() {
        //  执行下面这行代码 刚才new的Watcher实例 就被放到了dep.subs
        // 这个数组中
        Dep.target && dep.addsub(Dep.target);
        return val;
      },
    });
  });
}
// 对HTML结构进行模板编译的方法
function Compile(el, vm) {
  // 获取el对应的DOM元素
  vm.$el = document.querySelector(el);
  // 创建文档碎片 提高DOM操作的性能 （减少重绘）
  const fragment = document.createDocumentFragment();
  while ((childNode = vm.$el.firstChild)) {
    fragment.appendChild(childNode);
  }
  //   进行模板编译
  replace(fragment);
  //   将文档碎片插入到网页中
  vm.$el.appendChild(fragment);
  //   负责对DOM模板进行编译的方法
  function replace(node) {
    //   定义匹配插值表达式的正则
    const regMustache = /\{\{\s*(\S+)\s*\}\}/;
    // 证明当前的node节点是一个文本节点 需要进行正则的替换
    if (node.nodeType === 3) {
      // 文本节点也是一个DOM对象 如果要获取文本节点的字符串内容 需要textContent获取
      const text = node.textContent;
      const execResult = regMustache.exec(text);
      console.log(execResult);
      if (execResult) {
        const value = execResult[1].split(".").reduce((newobj, k) => {
          return newobj[k];
        }, vm);
        node.textContent = text.replace(regMustache, value);
        new Watcher(vm, execResult[1], (newVal) => {
          node.textContent = text.replace(regMustache, newVal);
        });
      }
      return;
    }
    if (node.nodeType === 1 && node.tagName.toUpperCase() === "INPUT") {
      const attrs = Array.from(node.attributes);
      // console.log(attrs);
      const res = attrs.find((x) => {
        return x.name == "v-model";
      });
      //   console.log(res);
      if (res) {
        //   获取到当前v-model的属性的值
        let newval = res.value.split(".").reduce((newobj, k) => newobj[k], vm);
        node.value = newval;
        //   创建watcher实例
        new Watcher(vm, res.value, (newval) => {
          node.value = newval;
        });
      }
      // 监听文本框的点击事件 拿到最新的input的值
      node.addEventListener("input", (e) => {
        const keyarr = res.value.split(".");
        const obj = keyarr.slice(0, keyarr.length - 1).reduce((newObj, k) => {
          return newObj[k];
        }, vm);
        obj[keyarr[keyarr.length - 1]] = e.target.value;
      });
    }
    // 如果node不是一个文本节点 则要将node的子节点进行遍历递归
    node.childNodes.forEach((child) => {
      replace(child);
    });
  }
}
// 依赖收集类 保存watch的
class Dep {
  constructor() {
    this.subs = [];
  }
  addsub(watcher) {
    this.subs.push(watcher);
  }
  notify() {
    this.subs.forEach((watch) => {
      watch.update();
    });
  }
}
// 订阅者类 在数据变化的时候更新自己的Dom内容
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm;
    this.key = key;
    this.cb = cb;
    // 下面三行代码 负责把Watcher的实例存放到Dep的subs数组中
    Dep.target = this;
    key.split(".").reduce((newobj, k) => {
      return newobj[k];
    }, vm);
    Dep.target = null;
  }
  update() {
    const val = this.key.split(".").reduce((newobj, k) => {
      return newobj[k];
    }, this.vm);
    this.cb(val);
  }
}
